#!/usr/bin/perl
use XML::Simple qw(:strict);
use Data::Dumper;
# this needs a lot of refactoring

my $file = '../redditApi.xml';
my $doc = XMLin($file, KeyAttr => 0, ForceArray => 1);
my $debug = shift;
my $outputDir = "Reddit/RedditApi";

print Dumper($doc) if $debug;
print CreateRedditApi($doc);

sub ApiNameToClassName
{
	my $name = shift;
	return sprintf("%s%sApi", uc(substr($name, 0, 1)), substr($name, 1));
}
sub ApiNameToVarName
{
	my $name = shift;
	return sprintf("%s%s", uc(substr($name, 0, 1)), substr($name, 1));
}
sub AddTab
{
	return [map { "\t$_" } @{shift()}];
}
sub PrintCode
{
	my $fileName = shift;
	my @code = @{shift()};
	open CODE, ">$fileName";
	print CODE "//If you're editing this by hand, you're doing something wrong\n";
	print CODE join "\n", @code;
	close CODE;
}
sub CreateRedditApi
{
	my $node = shift;
	my @text = ();

	push @text, 'using Reddit.EndpointRetriever;';
	push @text, 'namespace Reddit';
	push @text, '{';
	push @text, @{AddTab(CreateRedditApiBaseClass($node))};
	push @text, '}';
	PrintCode("$outputDir/RedditApiBase.cs", \@text);

	@text = ();
	push @text, 'using System;';
	push @text, 'using Reddit.EndpointRetriever;';
	push @text, 'using Reddit.Api.Parameters;';
	push @text, "namespace Reddit.Api"; 
	push @text, "{"; 
	foreach my $apiNode (@{$node->{api}})
	{
		push @text, @{AddTab(CreateApi($apiNode))};
	}
	push @text, "}";
	PrintCode("$outputDir/Api/Apis.cs", \@text);

	@text = ();
	push @text, "namespace Reddit.Api.Parameters";
	push @text, "{";
	foreach (map { @{$_->{endpoint}} } @{$node->{api}})
	{
		push @text, @{AddTab(CreateEndpointParameterClass($_))};
	}
	push @text, "}";
	PrintCode("$outputDir/Api/Parameters/Parameters.cs", \@text);
	#return join("\n", @text) . "\n";
}
sub CreateRedditApiBaseClass
{
	my $node = shift;
	push @text, "public class RedditApiBase<T>";
	push @text, "{";
	push @text, @{AddTab(CreateRedditApiBaseClassAccessors($node))};
	push @text, @{AddTab(CreateRedditApiBaseClassCtor($node))};
	push @text, "}";
	return \@text;
}
sub CreateRedditApiBaseClassAccessors
{
	my $node = shift;
	my @text = ();
	foreach (@{$node->{api}})
	{
		my $varName = ApiNameToVarName($_->{name});
		my $className = ApiNameToClassName($_->{name});
		push @text, "public readonly Api.$className<T> $varName;";
	}
	return \@text;
}
sub CreateRedditApiBaseClassCtor
{
	my $node = shift;
	my @text = ();
	push @text, "public RedditApiBase(IEndpointRetriever<T> endpointRetriever)";
	push @text, "{";
	foreach (@{$node->{api}})
	{
		my $varName = ApiNameToVarName($_->{name});
		my $className = ApiNameToClassName($_->{name});
		push @text, "\tthis.$varName = new Api.$className<T>(endpointRetriever);";
	}
	push @text, "}";
	return \@text;
}
sub CreateMainCtor
{
	my $node = shift;
	my @text = ();
	push @text, "public function RedditApiBase(\$endpointRetriever)";
	push @text, "{";
	push @text, @{AddTab(CreateMainCtorCode($node->{api}))};
	push @text, "}";
	\@text;
}
sub CreateMainCtorCode
{
	return [map { 
		my $varName = ApiNameToVarName($_->{name});
		my $className = ApiNameToClassName($_->{name});
		"\$this->$varName = new $className(\$endpointRetriever);";
	} @{shift()}];
}
sub CreateApiAccessors
{
	return [map { 'public $' . ApiNameToVarName($_->{name}) . ';' } @{shift()}];
}
sub CreateApi
{
	my $node = shift;
	my @text = ();
	my $className = ApiNameToClassName($node->{name});
	push @text, sprintf "public class %s<T> : Reddit.Api.ApiBase<T>", $className;
	push @text, '{';
	push @text, @{AddTab(CreateApiCtor($className))};

	foreach (@{$node->{endpoint}})
	{
		push @text, @{AddTab(CreateEndpoint($_))};
	}
	push @text, "}";
	\@text;
}
sub CreateApiCtor
{
	my $className = shift;
	return [
	"public $className(IEndpointRetriever<T> endpointRetriever) : base(endpointRetriever) {}",
	];
}
sub CreateEndpoint
{
	my $node = shift;
	my @text;
	#push @text, @{CreateEndpointParameterClass($node)};
	push @text, CreateEndpointFunctionDefinition($node);
	push @text, "{";
	push @text, @{AddTab(CreateEndpointFunctionCode($node))};
	push @text, "}";
	return \@text;
}
sub CreateEndpointParameterClass
{
	my $node = shift;
	my @text = ();
	push @text, "public class $node->{name}Parameters";
	push @text, '{';
	push @text, @{AddTab(CreateEndpointParameterVariables($node->{parameter}))};
	push @text, @{AddTab(CreateEndpointParameterCtor($node))};
	push @text, '}';
	return \@text;
}
sub CreateEndpointParameterVariables
{
	my @parameters = @{shift()};
	my @text = ();
	foreach (@parameters)
	{
		next if ($_->{static} eq 'true');
		push @text, "public string $_->{name} { get; set; }";
	}
	return \@text;
}
sub CreateEndpointParameterCtor
{
	my $node = shift();
	my @text = ();
	my @parameters = grep { $_->{static} ne 'true' && $_->{optional} ne 'true' } @{$node->{parameter}};
	push @text,
		"public $node->{name}Parameters(" .
	       	join(", ", map { "string $_->{name}" }  @parameters)
		. ")";
	push @text, "{";
	push @text, map { "\tthis.$_->{name} = $_->{name};" } @parameters;
	push @text, "}";
	return \@text;
}
sub CreateEndpointFunctionDefinition
{
	my $node = shift;
	my $text = sprintf "public T %s(%sParameters parameters)", $node->{name}, $node->{name};
	return $text;
}
sub CreateEndpointFunctionCode
{
	my $node = shift;
	my @parameters = @{$node->{parameter}};
	my @code = ();
	my $requiredParameters = 0;
	for (@parameters)
	{
		next if (($_->{static} eq 'true') || ($_->{optional} eq 'true'));
		$requiredParameters = 1;
		push @code, "if (parameters == null)";
		push @code, "\tthrow new Exception(\"Parameters cannot be null\");";
		last;
	}
	if (!$requiredParameters)
	{
		push @code, "if (parameters == null)";
		push @code, "\tparameters = new $node->{name}Parameters();";
	}
	push @code, 'Endpoint endpoint = new Endpoint();';
	foreach (@parameters)
	{
		if ($_->{static})
		{
			push @code, "endpoint.AddStaticPart(\"$_->{value}\");";
		} elsif ($_->{querystring} eq 'true') {
			my $value = 'parameters.' . $_->{name};
			my $format = $_->{format} ? "\"$_->{format}\"" : "null";
			$format =~ s/%s/{0}/g;
			my $name = $_->{name};
			push @code, "endpoint.AddOption(\"$name\", $value, $format);";
		} else {
			my $value = 'parameters.' . $_->{name};
			my $format = $_->{format} ? "\"$_->{format}\"" : "null";
			$format =~ s/%s/{0}/g;
			push @code, "endpoint.AddPart($value, $format);";
		}
	}
	push @code, 'return this.RetrieveEndpoint(endpoint);';
	return \@code;
}

